package codingforgreat.class05;


import thired.class11_digui.Class20_MinCoinsNoLimit;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

// 题目：
// 给定两个字符串s1和s2，问s2最少删除多少字符可以成为s1的子串？
// 比如 s1 = "abcde"，s2 = "axbc"
// 返回 1
public class CLass04_DeleteMinCost {
        //解法1：求s2子序列使用kmp在s1中验证
        public static int minCost1(String s1, String s2) {
            List<String> s2Subs = new ArrayList<>();
            process(s2.toCharArray(), 0, "", s2Subs);
            s2Subs.sort(new LenComp());
            for (String str : s2Subs) {
                if (s1.indexOf(str) != -1) { // indexOf底层和KMP算法代价几乎一样，也可以用KMP代替
                    return s2.length() - str.length();
                }
            }
            return s2.length();
        }

    public static void process(char[] str2, int index, String path, List<String> list) {
        if (index == str2.length) {
            list.add(path);
            return;
        }
        process(str2, index + 1, path, list);
        process(str2, index + 1, path + str2[index], list);
    }
    public static class LenComp implements Comparator<String> {

        @Override
        public int compare(String o1, String o2) {
            return o2.length() - o1.length();
        }

    }

    // 解法二
    // 生成所有s1的子串
    // 然后考察每个子串和s2的编辑距离(假设编辑距离只有删除动作且删除一个字符的代价为1)
    // 如果s1的长度较小，s2长度较大，这个方法比较合适O（N^3*M）
    public static int minCost2(String s1, String s2) {
        if (s1.length() == 0 || s2.length() == 0) {
            return s2.length();
        }
        int ans = Integer.MAX_VALUE;
        char[] str2 = s2.toCharArray();
        for (int start = 0; start < s1.length(); start++) {
            for (int end = start + 1; end <= s1.length(); end++) {
                // str1[start....end]
                // substring -> [ 0,1 )
                ans = Math.min(ans, distance(str2, s1.substring(start, end).toCharArray()));
            }
        }
        return ans == Integer.MAX_VALUE ? s2.length() : ans;
    }

    // 求str2到s1sub的编辑距离
    // 假设编辑距离只有删除动作且删除一个字符的代价为1
    public static int distance(char[] str2, char[] s1sub) {
        int row = str2.length;
        int col = s1sub.length;
        int[][] dp = new int[row][col];
        // dp[i][j]的含义：
        // str2[0..i]仅通过删除行为变成s1sub[0..j]的最小代价
        // 可能性一：
        // str2[0..i]变的过程中，不保留最后一个字符(str2[i])，
        // 那么就是通过str2[0..i-1]变成s1sub[0..j]之后，再最后删掉str2[i]即可 -> dp[i][j] = dp[i-1][j] + 1
        // 可能性二：
        // str2[0..i]变的过程中，想保留最后一个字符(str2[i])，然后变成s1sub[0..j]，
        // 这要求str2[i] == s1sub[j]才有这种可能, 然后str2[0..i-1]变成s1sub[0..j-1]即可
        // 也就是str2[i] == s1sub[j] 的条件下，dp[i][j] = dp[i-1][j-1]
        dp[0][0] = str2[0] == s1sub[0] ? 0 : Integer.MAX_VALUE;
        for (int j = 1; j < col; j++) {
            dp[0][j] = Integer.MAX_VALUE;
        }
        for (int i = 1; i < row; i++) {
            dp[i][0] = (dp[i - 1][0] != Integer.MAX_VALUE || str2[i] == s1sub[0]) ? i : Integer.MAX_VALUE;
        }
        for (int i = 1; i < row; i++) {
            for (int j = 1; j < col; j++) {
                dp[i][j] = Integer.MAX_VALUE;
                if (dp[i - 1][j] != Integer.MAX_VALUE) {
                    dp[i][j] = dp[i - 1][j] + 1;
                }
                if (str2[i] == s1sub[j] && dp[i - 1][j - 1] != Integer.MAX_VALUE) {
                    dp[i][j] = Math.min(dp[i][j], dp[i - 1][j - 1]);
                }

            }
        }
        return dp[row - 1][col - 1];
    }

    // 解法二的优化
    // 解法二的优化
    public static int minCost3(String s1, String s2) {
        if (s1.length() == 0 || s2.length() == 0) {
            return s2.length();
        }
        char[] str2 = s2.toCharArray();
        char[] str1 = s1.toCharArray();
        int M = str2.length;
        int N = str1.length;
        int[][] dp = new int[M][N];
        int ans = M;
        for (int start = 0; start < N; start++) { // 开始的列数
            dp[0][start] = str2[0] == str1[start] ? 0 : M;
            for (int row = 1; row < M; row++) {
                dp[row][start] = (str2[row] == str1[start] || dp[row - 1][start] != M) ? row : M;
            }
            ans = Math.min(ans, dp[M - 1][start]);
            // 以上已经把start列，填好
            // 以下要把dp[...][start+1....N-1]的信息填好
            // start...end end - start +2
            for (int end = start + 1; end < N && end - start < M; end++) {
                // 0... first-1 行 不用管
                int first = end - start;
                dp[first][end] = (str2[first] == str1[end] && dp[first - 1][end - 1] == 0) ? 0 : M;
                for (int row = first + 1; row < M; row++) {
                    dp[row][end] = M;
                    if (dp[row - 1][end] != M) {
                        dp[row][end] = dp[row - 1][end] + 1;
                    }
                    if (dp[row - 1][end - 1] != M && str2[row] == str1[end]) {
                        dp[row][end] = Math.min(dp[row][end], dp[row - 1][end - 1]);
                    }
                }
                ans = Math.min(ans, dp[M - 1][end]);
            }
        }
        return ans;
    }

    public static int minCostX(String s1, String s2) {
        char[] c1 = s1.toCharArray();
        char[] c2 = s2.toCharArray();
        int N = s2.length();
        int M = s1.length();


        // dp[i][j] 的含义 :
        // s2中前缀i长度的字符串，至少删除多少个字符可以变成 s1中前缀j长度字符串的 后缀串
        int[][] dp = new int[N + 1][M + 1];

        for (int i = 1; i <= N; i++) {
            dp[i][0] = i;
                // s2前缀i长度，需要都删掉，可以变成s1前缀0长度字符串的 后缀串
                for (int j = 1; j <= c1.length; j++) {
                    if (c2[i - 1] == c1[j - 1]) {
                        // 如果 c2[i-1] == c1[j-1]
                        // 可能性1 要么 c2[i-1] 和 c1[j-1] 进行匹配
                        // 问题回到 dp[i-1][j-1]
                        // 可能性2 要么 c2[i-1] 和 c1[j-1] 不进行匹配
                        // s2[0...i] 依然删除 s2[i] 问题回到 dp[i-1][j] + 1
                        // dp[i][j] = Math.min(dp[i-1][j-1], dp[i-1][j] + 1);
                        // 实际上
                        // 只需要考虑可能性1
                        // 例
                        // abc 去匹配 abc.....c
                        // 删掉 .....c 和 删掉 c..... 是一个意思
                        dp[i][j] = dp[i - 1][j - 1];
                    } else {
                        // 如果 c2[i-1] != c1[j-1]
                        // c2[i-1] 和 c1[j-1] 不进行匹配
                        // s2[0...i] 删除 s2[i] 问题回到 dp[i-1][j] + 1
                        dp[i][j] = dp[i - 1][j] + 1;
                    }
                }
            }
        int ans = dp[c2.length][0];
        for (int j = 1; j <= c1.length; j++) {
            ans = Math.min(ans, dp[c2.length][j]);
        }
        return ans;
    }






    // 为了测试
    public static String generateRandomString(int l, int v) {
        int len = (int) (Math.random() * l);
        char[] str = new char[len];
        for (int i = 0; i < len; i++) {
            str[i] = (char) ('a' + (int) (Math.random() * v));
        }
        return String.valueOf(str);
    }

    // 为了测试
    public static void main(String[] args) {
        int str1Len = 200;
        int str2Len = 100;
        int v = 5;
        int testTime = 10000;
        System.out.println("测试开始");
        for (int i = 0; i < testTime; i++) {
            String str1 = generateRandomString(str1Len, v);
            String str2 = generateRandomString(str2Len, v);
            int ans3 = minCost3(str1, str2);
//             同学提供的解法，最优解
            int ansX = minCostX(str1, str2);
            if (ans3 != ansX) {
                System.out.println("出错了!");
                System.out.println(str1);
                System.out.println(str2);
                System.out.println(ans3);
                System.out.println(ansX);
                break;
            }
        }
        System.out.println("测试结束");
    }



}
